package de.medieninf.sensimg;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;

/**
 * Main form that allows to turn off/on settings and
 * gives status information regarding acceleration data.
 * @author pb
 */
public class MainForm extends Canvas implements AccelerationListener {
    /**
     * Label for Sensor on/off.
     */
    private static final String LABEL_SENSOR = "Sensor ";
    /**
     * Label for Ball on/off.
     */
    private static final String LABEL_BALL = "Ball ";
    /**
     * Label for Send on/off.
     */
    private static final String LABEL_SEND = "Send ";

    /**
     * Status display of Sensor.
     */
    private String strSensor;
    /**
     * Status display of Ball.
     */
    private String strBall;
    /**
     * Status display of Send.
     */
    private String strSend;
    /**
     * Status of sensor.
     */
    private boolean sensor;
    /**
     * Status of ball.
     */
    private boolean ball;
    /**
     * Status of send.
     */
    private boolean send;
    /**
     * Which display font on Canvas.
     */
    private Font font;
    /**
     * The font height (cached).
     */
    private int fontHeight = 0;
    /**
     * Local cache of keyboardState.
     */
    private KeyboardState keyboardState;
    /**
     * acceleration data x.
     */
    private double x;
    /**
     * acceleration data y.
     */
    private double y;
    /**
     * acceleration data z.
     */
    private double z;
    /**
     * The largest acceleration value.
     */
    private double largest = 1.0;
    /**
     * The template to fill while sending acceleration data.
     */
    private char[] xyzbuf = {'x', ':', ' ', '-', '0', '.', '0', '0'};
    /**
     * Local cache of the settings instance.
     */
    private Settings settings = Settings.getSettings();
    /**
     * Local cache of the SensorService instance.
     */
    private SensorService sensorServive = SensorService.getSensorService();

    /**
     * Command to turn Sensor on.
     */
    private Command cmdSensorOn = new Command("Sensor On", Command.OK, 1);
    /**
     * Command to turn Sensor off.
     */
    private Command cmdSensorOff = new Command("Sensor Off", Command.OK, 1);
    /**
     * Command to turn Ball on.
     */
    private Command cmdBallOn = new Command("Ball On", Command.OK, 2);
    /**
     * Command to turn Ball off.
     */
    private Command cmdBallOff = new Command("Ball Off", Command.OK, 2);
    /**
     * Command to turn Sender on.
     */
    private Command cmdSendOn = new Command("Send On", Command.OK, 3);
    /**
     * Command to turn Sender off.
     */
    private Command cmdSendOff = new Command("Send Off", Command.OK, 3);

    /**
     * CommandListener of creator.
     */
    private CommandListener upperCl;
    /**
     * Commands to be inserted according to creator.
     */
    private CommandListener cl = new CommandListener() {
        public void commandAction(final Command c, final Displayable d) {
            if (c == cmdSensorOn) {
                settings.setSensor(true);
            } else if (c == cmdSensorOff) {
                settings.setSensor(false);
            } else if (c == cmdBallOn) {
                settings.setBall(true);
            } else if (c == cmdBallOff) {
                settings.setBall(false);
            } else if (c == cmdSendOn) {
                settings.setSend(true);
            } else if (c == cmdSendOff) {
                settings.setSend(false);
            } else {
                upperCl.commandAction(c, d);
            }
        }
    };
    /**
     * Updater to be run on each change of settings.
     */
    private Runnable updater = new Runnable() {
        public void run() {
            if (settings.getSensor()) {
                sensor = true;
                removeCommand(cmdSensorOn);
                addCommand(cmdSensorOff);
            } else {
                sensor = false;
                removeCommand(cmdSensorOff);
                addCommand(cmdSensorOn);
            }
            if (settings.getBall()) {
                ball = true;
                removeCommand(cmdBallOn);
                addCommand(cmdBallOff);
            } else {
                ball = false;
                removeCommand(cmdBallOff);
                addCommand(cmdBallOn);
            }
            if (settings.getSend()) {
                send = true;
                removeCommand(cmdSendOn);
                addCommand(cmdSendOff);
            } else {
                send = false;
                removeCommand(cmdSendOff);
                addCommand(cmdSendOn);
            }
            strSensor = LABEL_SENSOR + (sensor ? "On" : "Off");
            strBall = LABEL_BALL + (ball ? "On" : "Off");
            strSend = LABEL_SEND + (send ? "On" : "Off");
        }
    };

    /**
     * Constructor.
     * @param pupperCl the commandListener of the creator
     * @param cmds the commands of the creator
     * @param pkeyboardState the keyboardState to be used.
     */
    public MainForm(final CommandListener pupperCl, final Command[] cmds,
            final KeyboardState pkeyboardState) {
        this.keyboardState = pkeyboardState;
        this.upperCl = pupperCl;
        for (int i = 0; i < cmds.length; i++) {
            addCommand(cmds[i]);
        }
        font = Font.getFont(Font.FACE_PROPORTIONAL,
                            Font.STYLE_BOLD, Font.SIZE_MEDIUM);
        fontHeight = font.getHeight();
        settings = Settings.getSettings();
        settings.registerUpdate(updater);
        updater.run();
        setCommandListener(cl);
        sensorServive.register(this);
    }

    /**
     * Converts an int 0 <= v <= 9 to a character.
     * @param v the int to convert
     * @return the respective character
     */
    private char int2char(final int v) {
        switch(v) { // should be fast
        case 0: return '0';
        case 1: return '1';
        case 2: return '2';
        case 3: return '3';
        case 4: return '4';
        case 5: return '5';
        case 6: return '6';
        case 7: return '7';
        case 8: return '8';
        case 9: return '9';
        default:
            String mesg = "int2char: shouldn't be larger than 9 or negative";
            throw new RuntimeException(mesg);
        }
    }

    /**
     * Converts a double value to a String and puts it in a char array.
     * @param pd the double value
     * @param s the character array
     * @return the very same character array
     */
    private char[] toCharArray(final double pd, final char[] s) {
        double d = pd;
        if (d < 0) {
            s[3] = '-';
            d = -d;
        } else {
            s[3] = ' ';
        }
        if (d >= 9.99) {
            s[4] = s[6] = s[7] = '9';
            return s;
        }
        s[4] = int2char((int) d);
        d -= ((int) d);
        d *= 10;
        s[6] = int2char((int) d);
        d -= ((int) d);
        d *= 10;
        s[7] = int2char((int) d);
        return s;
    }

    /**
     * Draws the overview screen.
     * @param g Graphics
     */
    protected final void paint(final Graphics g) {
        int width = getWidth();
        int height = getHeight();
        g.setColor(Colors.WHITE);
        g.fillRect(0, 0, getWidth(), getHeight());
        g.setFont(font);
        g.setColor(sensor ? Colors.GREEN : Colors.RED);
        g.drawString(strSensor, 0, 0, Graphics.TOP | Graphics.LEFT);
        g.setColor(ball ? Colors.GREEN : Colors.RED);
        g.drawString(strBall, width / 2, 0, Graphics.TOP | Graphics.HCENTER);
        g.setColor(send ? Colors.GREEN : Colors.RED);
        g.drawString(strSend, width, 0, Graphics.TOP | Graphics.RIGHT);
        g.setColor(Colors.BLACK);
        xyzbuf[0] = 'x';
        g.drawChars(toCharArray(x, xyzbuf), 0, xyzbuf.length, 0, fontHeight,
                    Graphics.TOP | Graphics.LEFT);
        xyzbuf[0] = 'y';
        g.drawChars(toCharArray(y, xyzbuf), 0, xyzbuf.length,
                    width / 2, fontHeight,
                    Graphics.TOP | Graphics.HCENTER);
        xyzbuf[0] = 'z';
        g.drawChars(toCharArray(z, xyzbuf), 0, xyzbuf.length, width, fontHeight,
                    Graphics.TOP | Graphics.RIGHT);
        int xoffset = 0;
        int yoffset = fontHeight * 2;
        paintBall(g, width - xoffset, height - yoffset, xoffset, yoffset);
    }

    /**
     * The message if no ball is shown.
     */
    static final char[] NOBALLMSG = {'n', 'o', ' ', 'b', 'a' , 'l', 'l' };
    /**
     * max degrees.
     */
    static final int MAX_DEGREES = 360;
    /**
     * Paints the ball on the canvas.
     * @param g the graphics on the canvas
     * @param w the width
     * @param h the height
     * @param xoffset the x offset
     * @param yoffset the y offset
     */
    private void paintBall(final Graphics g, final int w, final int h,
            final int xoffset, final int yoffset) {
        g.setClip(xoffset, yoffset, w, h);
        g.setColor(Colors.WHITE);
        g.fillRect(xoffset, yoffset, w, h);
        if (!ball) {
            g.setColor(Colors.BLACK);
            g.drawChars(NOBALLMSG, 0, NOBALLMSG.length,
                        xoffset + w / 2, yoffset + h / 2,
                        Graphics.BASELINE | Graphics.HCENTER);
            return;
        }
        if (sensorServive.isMonitoring()) {
            largest = sensorServive.getLargest();
        }
        x = -x / largest;
        y = y / largest;
        z = (-z + largest) / largest; // offset gravity
        int radius = (int) (z * w / 2);
        double whalf = (double) w/2;
        double hhalf = (double) h/2;
        int cx = (int) (whalf + x * whalf);
        int cy = (int) (hhalf + y * hhalf);
        if (z >= 0) {
            g.setColor(Colors.BLUE);
        } else {
            g.setColor(Colors.RED);
            radius = -radius;
        }
        if (radius < 2 * 2) {
            radius = 2 * 2;
        }
        g.fillArc(xoffset + cx - radius / 2,
                  yoffset + cy - radius / 2,
                  radius, radius, 0, MAX_DEGREES);
    }

    /**
     * AccelerationListener callback.
     * @param px x value
     * @param py y value
     * @param pz z value
     */
    public final void update(final double px, final double py,
            final double pz) {
        this.x = px;
        this.y = py;
        this.z = pz;
        repaint();
    }

    /**
     * Connect key press events.
     * @param keyCode the keyCode
     */
    protected final void keyPressed(final int keyCode) {
        keyboardState.onKeyDown(keyCode, getGameAction(keyCode));
        super.keyPressed(keyCode);
    }
    /**
     * Connect key release events.
     * @param keyCode the keyCode
     */
    protected final void keyReleased(final int keyCode) {
        keyboardState.onKeyUp(keyCode, getGameAction(keyCode));
        super.keyReleased(keyCode);
    }
}
